﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace library
{

    public partial class Client : IDisposable
    {
        public delegate void SearchReturnHandler(string search, ValueHits<p2pPost> post);

        public static event SearchReturnHandler OnSearchReturn;


        public delegate void FileDownloadHandler(byte[] address, string filename, string speficFilena = null);

        public static event FileDownloadHandler OnFileDownload;


        public delegate void FileUploadHandler(string filename, string base64Address);

        public static event FileUploadHandler OnFileUpload;

        static Cache<string> searching = new Cache<string>(2000);

        internal static Peer LocalPeer;

        internal static int P2pPort;

        internal static byte[] P2pAddress;

        public static IDisposable Start(byte[] p2pAddress, int p2pPort)
        {
            P2pAddress = p2pAddress;

            P2pPort = p2pPort;
            
            Network.Configure();

            LocalPeer = Peer.CreateLocalPeer();

            DelayedWrite.Start();

            p2pServer.Start();

            p2pRequest.Start();

            Peers.Start();

            LocalPackets.Start();

            LocalPosts.Load();

            p2pFile.Queue.Load();

            return new Client();
        }

        internal static bool Stop
        {
            get;
            private set;
        }

        public static void Close()
        {
            Stop = true;

            p2pServer.Stop();

            LocalPackets.Stop();

            Peers.Stop();

            p2pRequest.Stop();

            //LocalIndex.Save();

            LocalPosts.Save();

            DelayedWrite.Stop();
        }

        public static string GetWelcomeKey()
        {
            if (LocalPeer.EndPoint.Address.Equals(IPAddress.Any))
                return string.Empty;

            return Utils.ToBase64String(Addresses.ToBytes(LocalPeer.EndPoint));
        }

        public static bool AnyPeer()
        {
            return Peers.Any();
        }

        public static Dictionary<int, double> GetPeers()
        {
            Dictionary<int, double> result = new Dictionary<int, double>();

            Peer[] pp = Peers.GetPeers();

            foreach (Peer p in pp)
                result.Add(p.EndPoint.Port, p.Distance.Median);

            return result;
        }

        public static void GetPeer(string base64EndPoint)
        {
            byte[] b = Convert.FromBase64String(base64EndPoint.Replace('-', '/').Replace('(', '+')); //Utils.FromBase64String(base64EndPoint);

            IPEndPoint endpoint = new IPEndPoint(
                new IPAddress(b.Take(Parameters.ipv4Addresssize).ToArray()),
                BitConverter.ToUInt16(b, Parameters.ipv4Addresssize));

            Peer peer = Peers.GetPeerOrCreate(endpoint);

            p2pRequest.Send(targetPeer: peer, command: RequestCommand.Peer, returnPeer: peer);
        }

        public static void Search(string key)
        {
            lock (searching)
            {
                if (searching.Any(x => x.CachedValue.Equals(key)))
                    return;

                searching.Add(key);
            }

            var address = Utils.FromBase64String(key);

            var posts = new ValueHits<p2pPost>();

            if(address == null)
                posts = LocalIndex.SearchPostsAddressesByTerm(key);

            if(address != null)
            {
                posts.AddRange(LocalPosts.Search(address));
            }

           

            SearchReturn(key, posts);


            int requisitions = 0;

            byte[] byte_key = Encoding.Unicode.GetBytes(key);

            while (requisitions < Parameters.propagation)
            {
                var sent = p2pRequest.Send(
                        address: BitConverter.GetBytes(byte_key.Length).Concat(byte_key).ToArray(),
                        command: RequestCommand.Metadata,
                        ignoreDeadEnd: true,
                        wait: false);

                if (sent)
                    requisitions++;
                else
                    break;
            }

        }

        internal static void DownloadComplete(byte[] address, string filename, string speficFilena = null)
        {
            if (OnFileDownload != null)
                OnFileDownload(address, filename, speficFilena);
        }


        static void SearchReturn(string search, ValueHits<p2pPost> post)
        {
            if (OnSearchReturn != null)
                OnSearchReturn(search, post);
        }

        public static byte[] Post(string post,string address = null, string target = null)
        {
            p2pPost p = p2pPost.Create(post, Utils.FromBase64String(address), Utils.FromBase64String(target));

            return p.Address;
        }

        public static void Upload(string filename, byte[] address = null, byte[] userAddress = null)
        {
            Log.Write("[Upload] " + Utils.ToBase64String(p2pFile.Upload(filename, address, userAddress)) + " " + filename);
        }

        public static byte[] GetPost(byte[] address)
        {
            var res = LocalPosts.Get(address);

            if(res == null)
                return null;

            return res.Data;
        }

        public static byte[] Get(string base64Address)
        {
            var address = Utils.FromBase64String(base64Address);

            if (address == null)
                return null;

            return GetPost(address);
        }

        public static void Download(string base64Address, string filename, string specifItem = null)
        {
            p2pFile.Queue.Add(base64Address, filename, specifItem);
        }

        void IDisposable.Dispose()
        {
            Close();
        }

        internal static void BootStrap()
        {
            var post = p2pPost.Create("Extensão", null, VirtualAttributes.Extension);

            
        }
    }
}
